package com.lin.java.tool.file.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.crypto.digest.MD5;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lin.java.tool.common.enums.IsEnum;
import com.lin.java.tool.file.dao.po.SysFileConfigEntity;
import com.lin.java.tool.file.model.FileContext;
import com.lin.java.tool.file.model.FileDownload;
import com.lin.java.tool.file.model.FileRecord;
import com.lin.java.tool.file.service.FileService;
import com.lin.java.tool.file.service.chain.FileChain;
import com.lin.java.tool.file.service.vo.ChunkVO;
import com.lin.java.tool.general.dao.mapper.SysFileMapper;
import com.lin.java.tool.general.dao.po.SysFileEntity;
import com.lin.java.tool.general.enums.OssEnum;
import com.lin.java.tool.general.ex.CustomFileException;
import com.lin.java.tool.oss.model.dto.DownloadRespDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author: linjiang
 * @date: 2025/1/21
 */
@Slf4j
@Service
public class FileServiceImpl implements FileService {
    private final static String ROOT = System.getProperty("user.dir") + "/doc/temp";

    @Autowired
    private FileConfigServiceImpl fileConfigService;
    @Autowired
    private SysFileMapper fileMapper;
    @Autowired(required = false)
    private FileChain fileChain;

    @Override
    public String uploadFile(MultipartFile file) throws Exception {
        List<SysFileConfigEntity> fileConfigList = fileConfigService.lambdaQuery().eq(SysFileConfigEntity::getStatus, IsEnum.YES.getCode()).list();
        if (CollectionUtil.isEmpty(fileConfigList)) {
            throw new CustomFileException(OssEnum.FILE_FAIL_UPLOAD.getCode(), "文件配置不存在或未启用!");
        }

        // 文件日志
        List<FileRecord> records = new ArrayList<>(16);
        FileContext context = new FileContext();
        context.setFile(file);
        context.setConfigList(fileConfigList);
        fileChain.doUpload(context, records);

        if (records.size() == 0) {
            log.info("文件上传失败，文件配置支持的服务无法使用。。。。。。");
            throw new CustomFileException(OssEnum.FILE_FAIL_UPLOAD.getCode(), "文件上传失败!");
        }

        // 保存上传数据到数据库
        String uuid = UUID.fastUUID().toString(true);
        records.stream().map(item -> {
            SysFileEntity fileRecord = new SysFileEntity();
            fileRecord.setSomeObj(uuid);
            BeanUtils.copyProperties(item, fileRecord);
            return fileRecord;
        }).forEach(item -> fileMapper.insert(item));
        return uuid;
    }

    @Override
    public void downloadFile(String someObj, HttpServletResponse response) throws IOException {
        // 查询文件
        List<SysFileEntity> fileRecords = fileMapper.selectList(new QueryWrapper<SysFileEntity>().lambda().eq(SysFileEntity::getSomeObj, someObj));
        if (CollectionUtil.isEmpty(fileRecords)) {
            throw new CustomFileException(OssEnum.FILE_FAIL_DOWNLOAD.getCode(), "文件不存在!");
        }

        FileContext context = new FileContext();
        List<SysFileConfigEntity> configs = fileConfigService.lambdaQuery().list();
        context.setConfigList(configs);
        // 相同文件在不同平台，只需要从一个平台成功下载即可
        List<FileRecord> records = fileRecords.stream().map(item -> {
            FileRecord record = new FileRecord();
            BeanUtils.copyProperties(item, record);
            return record;
        }).collect(Collectors.toList());
        context.setRecords(records);

        FileDownload download = fileChain.doDownload(context);
        if (download == null) {
            throw new CustomFileException(OssEnum.FILE_FAIL_DOWNLOAD);
        }

        FileRecord record = download.getRecord();
        InputStream inputStream = download.getInputStream();
        // 下载
        response.reset();
        response.setContentType("application/octet-stream");
        response.setContentLength(record.getSize().intValue());
        response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(record.getOriginFileName(), "utf-8"));
        IoUtil.write(response.getOutputStream(), true, IoUtil.readBytes(inputStream));
    }


    @Override
    public void uploadChunk(ChunkVO chunk) throws IOException {
        // 文件md5。同一个文件的md5值相同。不在于文件名是否同与不同
        File f = new File(ROOT, chunk.getMd5());
        if (!f.exists()) {
            f.mkdirs();
        }

        // 文件分片内容的md5
        String md5 = MD5.create().digestHex(chunk.getFile().getInputStream());
        File ff = new File(f, chunk.getChunk() + "-" + md5 + ".part");

        if (ff.exists()) {
            System.out.println("文件秒传");
        }

        // 分片上传
        try (RandomAccessFile accessFile = new RandomAccessFile(ff, "rw")) {
            accessFile.write(chunk.getFile().getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void mergeChunk(ChunkVO chunk) {
        File f = new File(ROOT, chunk.getMd5());
        if (f.exists()) {
            List<File> list = new ArrayList<>();

            File[] files = f.listFiles();
            for (int i = 0; i < files.length; i++) {
                int temp = i;
                Optional<File> optional = Arrays.stream(files).filter(file -> file.getName().startsWith("" + temp)).findFirst();
                list.add(optional.get());
            }

            try (FileOutputStream outputStream = new FileOutputStream(new File(f, chunk.getName()))) {
                for (File shardFile : files) {
                    FileUtil.writeToStream(shardFile, outputStream);

                    // 删除分片
                    shardFile.delete();
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}
